package com.zqweb.gulimall.product.web;

import com.zqweb.gulimall.product.entity.CategoryEntity;
import com.zqweb.gulimall.product.service.CategoryService;
import com.zqweb.gulimall.product.vo.Catelog2Vo;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Controller
public class IndexController {
    @Autowired
    private CategoryService categoryService;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    // 专门用于处理前端index首页的controller
    @GetMapping({"/", "/index.html"})
    public String indexHandle(Model model) {// 起到ModelAndView返回类型的效果
        List<CategoryEntity> categoryEntities = categoryService.getOneLevelData();
        model.addAttribute("categorys", categoryEntities);
        // 无论是/ 或 /index.html的请求，都映射处理并路由到index.html首页去
        return "index";// 省略了前缀classpath:/templates/以及后缀.html，跳转到首页（视图解析器）
    }

    /*
     在页面加载完毕后（此时还没有真正展现给用户看），再去获取二级三级的所有分类数据并渲染
      */
    @GetMapping("/index/catalog.json")
    @ResponseBody
    public Map<String, List<Catelog2Vo>> getCatelogJson() {
//        System.out.println("进入getCatelogJson方法...");
        Map<String, List<Catelog2Vo>> cateMap = categoryService.getCatelogJsonWithRedisLock();
        return cateMap;
    }

    @GetMapping("/redisson/lock/test")
    @ResponseBody
    public String redissonLockTest() {
        // 新建1个名为my-lock的分布式可重入锁，在连接的Redis-Server中会新建1个缓存记录
        RLock lock = redissonClient.getLock("my-lock");// key为UUID-线程id
        // 上锁，默认上锁时间为30s（LockWatchdogTimeout），具有阻塞式等待特点（while循环），会一直阻塞等待，直到获取到相应的锁
//        lock.lock();
        /*
            1）如果未指定锁的超时时间TTL，就会发送给Redis相应的执行脚本，进行占锁，默认超时时间就是我们设定的TTL
            2）如果指定了锁的超时时间TTL，就使用30 * 1000作为超时时间（LockWatchdogTimeout看门狗的默认时间）
              只要占锁成功，就会启动1个定时任务（重新给锁设置过期时间，新的过期时间就是LockWatchdogTimeout 30s），
              而且每隔10s（internalLockLeaseTime--看门狗时间 / 3）就会自动续期（看门狗机制）
         */
        // 10s后自动解锁，而且锁时间到了以后不会自动续期，因此自动解锁时间一定要大于业务的执行时间
        lock.lock(10, TimeUnit.SECONDS);// 推荐这种开发写法，省去了整个过程中的自动预期步骤

        /*
        Redisson提供的锁的特点：
         1）锁的自动续期性。对于超长业务，运行期间会自动在当前锁快要过期时为其续上新的30s，
            不必担心业务处理时间过长，锁过期会自动被删除的问题
         2）在加锁业务执行完毕，不会再为当前锁自动续期，而且即使不手动释放锁，默认也会在30s后被自动删除
         */

        // 上锁的业务执行代码
        try {
            System.out.println("上锁成功，执行业务代码，此时线程id = " + Thread.currentThread().getId());
            Thread.sleep(10000);
        } catch (Exception e) {
            // 不对异常作任何处理
        } finally {
            System.out.println("释放锁... 线程id = " + Thread.currentThread().getId());
            // 手动解锁
            lock.unlock();
        }
        return "redisson框架的lock锁测试~";
    }


    /*
        ReadWriteLock：读写锁    读锁（也称：共享锁）  写锁（也称：排他锁、互斥锁、独享锁）
        保证读取的数据是最新的、准确的，即写锁在更改某个key的缓存记录值时，读锁不能去
        读取该key的缓存记录！
     */
    @GetMapping("/redisson/write")
    @ResponseBody
    public String redissonReadLockTest1() {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("lock-rw");
        RLock writeLock = readWriteLock.writeLock();
        String s = UUID.randomUUID().toString();
        try {
            writeLock.lock();// 看门狗机制，添加写锁
            System.out.println("获取写锁...");
            //添加指定key，value的缓存记录，限制了读写锁对应的另一个读锁的记录读取
            stringRedisTemplate.opsForValue().set("rwKey", s);
            Thread.sleep(15000);
        } catch (Exception e) {
            //
        } finally {
            writeLock.unlock();
            System.out.println("写锁释放...");
        }
        return s;
    }

    @GetMapping("/redisson/read")
    @ResponseBody
    public String redissonReadLockTest2() {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("lock-rw");
        RLock readLock = readWriteLock.readLock();
        String s = "";
        try {
            readLock.lock();// 看门狗机制，添加读锁
            System.out.println("获取读锁...");
            // 读取指定key的缓存记录值，只是添加了1个写锁限制
            s = stringRedisTemplate.opsForValue().get("rwKey");
            Thread.sleep(15000);
        } catch (Exception e) {
            //
        } finally {
            readLock.unlock();
            System.out.println("读锁释放...");
        }
        return s;
    }

    @GetMapping("/redisson/countdownLatch")
    @ResponseBody
    // 闭锁使用测试（放假案例--计数：所有班级都走完了，门卫才真正宣布放假！）
    public String redissonCountdownLatchTest() {
        // 并没有添加缓存记录
        RCountDownLatch countDownLatch = redissonClient.getCountDownLatch("cl-lock");
        countDownLatch.trySetCount(5L);
        try {
            // 当前计数为0，才结束等待，否则一直阻塞着
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "放假了...";
    }

    @GetMapping("/redisson/count/{id}")
    @ResponseBody
    public String redissonCountTest(@PathVariable("id") Long id) {
        RCountDownLatch countDownLatch = redissonClient.getCountDownLatch("cl-lock");
        countDownLatch.countDown();// 计数总量每次 - 1
        return "当前班级" + id + "已经离开...";
    }

    @GetMapping("/redisson/semaphore/park")
    @ResponseBody
    // Redisson之信号量机制测试（停车案例：停车位 > 0才可以停，车走了：停车位 + 1，停车了：停车位 - 1）
    // 可用于分布式的限流（流量上限，超过了该怎么做，没超过又该怎么做）
    public String redissonSemaphore_AcquireTest() {
        RSemaphore semaphore = redissonClient.getSemaphore("signal");
        boolean flag = true;
        try {
            // 获取1个信号量
//            semaphore.acquire();//  注意：信号容量 > 0，才能获取，否则会一直处于阻塞中（阻塞式获取）
            flag = semaphore.tryAcquire();// 尝试获取，有则返回true，没有则返回false。不会一直阻塞住
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "停车" + flag;
    }

    @GetMapping("/redisson/semaphore/go")
    @ResponseBody
    public String redissonSemaphore_ReleaseTest() {
        RSemaphore semaphore = redissonClient.getSemaphore("signal");
        semaphore.release();
        // 注意：信号容量 > 0，才可以进行释放，否则会一直处于阻塞中
        return "成功释放1个车位！";
    }


}
